#ifndef __HEAD_cuh__
#define __HEAD_cuh__

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <time.h>
#include <sstream>
#include <stdexcept>



/* cuda libs:
	cudart.lib
	cublas.lib
	curand.lib
	cusolver.lib
	cusparse.lib
*/
#include <cuda_runtime.h> 
#include <device_launch_parameters.h>
#include <cublas_v2.h>
#include <curand.h>
#include <cusolverDn.h>
#include <cusparse.h>

inline void checkSparse(cusparseStatus_t err, char const * file, unsigned int line){
	if ((err) != CUSPARSE_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUSPARSE error " << err << " at " << file << ":" << line;
		throw std::runtime_error(ss.str());
	}
}

inline void checkSolver(cusolverStatus_t err, char const * file, unsigned int line){
	if ((err) != CUSOLVER_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUSOLVER error " << err << " at " << file << ":" << line;
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaRand(curandStatus_t err, char const * file, unsigned int line){
	if ((err) != CURAND_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CURAND error " << err << " at " << file << ":" << line;
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaError(cudaError_t err, char const * file, unsigned int line){
	if (err != cudaSuccess){
		//std::stringstream ss;
		//ss << "CUDA error " << err<<" "<<cudaGetErrorString(err) << " at " << file << ":" << line;
		printf("%s in %s at line %d\n", cudaGetErrorString(err), file, line);
		system("pause");
		exit(1);
		//printf("%s\n", ss.str().c_str());
		//throw std::runtime_error(ss.str());
	}
}

inline void check(bool bTrue, const char* msg, char const * file, unsigned int line){
	if (!bTrue){
		std::stringstream ss;
		ss << "Error: \"" << msg << "\" at " << file << ":" << line;
		throw std::runtime_error(ss.str());
	}
}

inline void checkCudaStatus(cublasStatus_t status, char const * file, unsigned int line){
	if (status != CUBLAS_STATUS_SUCCESS){
		std::stringstream ss;
		ss << "CUDA status " << status << " at " << file << ":" << line;
		throw std::runtime_error(ss.str());
	}
}

#define CHECK_PARAM(x, msg)   check((x), (msg), __FILE__, __LINE__)
#define CHECK_CUDA(err) checkCudaError((err), __FILE__, __LINE__)
#define CHECK_CUDA_STATUS(cudaStatus) checkCudaStatus((cudaStatus),__FILE__,__LINE__)
#define CHECK_CURAND(err) checkCudaRand((err),__FILE__,__LINE__)
#define CHECK_CUSOLVER(err) checkSolver((err),__FILE__,__LINE__)
#define CHECK_CUSPARSE(err) checkSparse((err),__FILE__,__LINE__)

#define UIviewFileName "small_ratings.txt"
#define UUviewFileName "small_links.txt"

#define nUsers 5014
#define nItems 5487
#define nRatings 367126
#define nLinks 302788
#define M_ADD_OPS 1
#define M_MINUS_OPS 2
#define M_TIMES_OPS 3
#define M_DIVIDE_OPS 4

struct Params
{
	float split_rate;
	float maxS;
	float minS;
	int num_of_kmeans;
	int num_of_anchors;
	int r;
	int num_of_LAE_iters;
	int num_of_nearest_anchors;

	float alpha;
	float beta;
	float rho1;
	float rho2;
};
struct CooMat{
	float *vals;
	int *rows_ind;
	int *cols_ind;
	int nnz;
	int row_di;
	int col_di;
};
struct CooMatOnDevice{
	float *vals;
	int *rows_ind;
	int *cols_ind;
	int nnz;
	int row_di;
	int col_di;
};
struct CsrMat{
	float *vals;
	int *cols_ind;
	int *row_ptr;
	int nnz;
	int row_di;
	int col_di;
};
struct CsrMatOnDevice{
	float *vals;
	int *cols_ind;
	int *row_ptr;
	int nnz;
	int row_di;
	int col_di;
};
void read_data_2_coo_mat(CooMat &cooUIview, CooMat &cooUIview_test, CooMat &cooUUview, float split_rate);

void get_kernel_config(int sz_y, int sz_x, dim3& szGrid, dim3& szBlock
	, int& rowPerThread, int& colPerThread);

void kMeans(float *d_anchors, 
	const int num_of_clusters, 
	const CsrMatOnDevice &d_csrNDview, 
	const int num_of_keans_iters);

void get_anchors_by_Kmeans(float *d_anchors, 
	const CsrMatOnDevice &d_csrNDview, 
	const Params &params);

void quicksort(float *a, int l, int h, bool isDesc = true);
void LAE_process(const float *x, const unsigned int nDim,
	const float *U, const unsigned int nS,
	float *val_one_line,/* out */
	const unsigned int num_of_LAE_iters,
	cublasHandle_t cublashandle);

void get_Z(float *d_uZ, const int num_of_anchors, CsrMatOnDevice &d_csrUIview, CsrMat &h_csrUIview, const float *d_anchors_of_uiview, const float *h_anchors_of_uiview, CsrMatOnDevice &d_csrUUview, CsrMat &h_csrUUview, const float *d_anchors_of_uuview, const float *h_anchors_of_uuview, const Params &params, cublasHandle_t cublashandle);
void get_Z(float *h_iZ, const int num_of_anchors, CsrMatOnDevice &d_csrIUview, CsrMat &h_csrIUview, const float *d_anchors_of_iuview, const float *h_anchors_of_iuview, Params &params, cublasHandle_t cublashandle);
void get_iZ_by_LAE(const float *IUview, const float* anchors_of_iuview,
	const unsigned int nusers, const unsigned int nitems, const unsigned int nanchors,
	float *iZ,
	const unsigned int num_of_nearest_anchors,
	const unsigned int num_of_LAE_iters,
	cublasHandle_t cublashandle);

void get_uZ_by_LAE(const float* UIview, const float* UUview,
	const float* anchors_of_uiview, const float* anchors_of_uuview,
	const unsigned int nusers, const unsigned int nitems, const unsigned int nanchors,
	float* uZ,
	const unsigned int num_of_nearest_anchors,
	const unsigned int num_of_LAE_iters,
	cublasHandle_t cublashandle);

void get_S_from_Z(float *S, const float *Z, const unsigned int nObjs, const unsigned int nDim,
	cublasHandle_t cublashandle);
void MVDCF(
	float *B,/* out1: r X nusers*/
	float *D,/* out2: r X nitems*/
	const Params &params,
	const float *UIview, /*nusers X nitems float*/
	const float *IUview, /*nitems X nusers float*/
	float *d_S_i,/*nitems X nitems*/
	float *d_S_u,/*nusers X nusers*/
	cublasHandle_t cublashandle);
void write_test_data(const float *UIview_test,
	const int m,
	const int n,
	char *filename);
void write_into_file(float *B, int r, int n, char *filename);

void evaluate(const float *B,/* r X m 1.0f or -1.0f*/
	const float *D, /* r X n */
	const int r,
	const int m,
	const int n,
	const float *UIview_test,/* m X n [0.0, 5.0]*/
	const int result_num,
	cublasHandle_t cublashandle
	);
void read_from_file(float *B, const int r, const int n, char *filename);
void read_test_data(float *UIview_test, const int m, const int n,
	char *filename);
#endif